Offline chatting using a ESP32 code
#include <DNSServer.h>
#include <WebServer.h>
#include <vector>
// Wi-Fi Configuration (Open Network)
const char* ssid = "FREE CHAT";
// DNS and Web Server Settings
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 4, 1);
DNSServer dnsServer;
WebServer server(80);
// Structure to store messages
struct Message {
String sender;
String text;
};
std::vector<Message> chatHistory;
const size_t MAX_MESSAGES = 15; // Image support ke liye memory limit ko 15 kiya taaki crash na ho
// JSON Escape helper to handle special characters and long strings safely
String escapeJSON(String input) {
input.replace("\\", "\\\\");
input.replace("\"", "\\\"");
input.replace("\n", "\\n");
input.replace("\r", "");
return input;
}
// HTML, CSS, aur JavaScript (With Image Compression & Upload Feature)
const char HTML_INDEX[] PROGMEM = R"=====(
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Free Chat Hub</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body {
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
color: #f8fafc;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
header {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
padding: 15px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
.pulse {
width: 10px;
height: 10px;
background: #10b981;
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
#chat-container {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.msg {
max-width: 75%;
padding: 12px 16px;
border-radius: 16px;
font-size: 15px;
line-height: 1.4;
animation: fadeIn 0.3s ease-out forwards;
opacity: 0;
transform: translateY(10px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
@keyframes fadeIn {
to { opacity: 1; transform: translateY(0); }
}
.msg.self {
align-self: flex-end;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
color: white;
border-bottom-right-radius: 4px;
}
.msg.other {
align-self: flex-start;
background: rgba(255, 255, 255, 0.07);
border-bottom-left-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.meta {
font-size: 11px;
opacity: 0.6;
margin-bottom: 4px;
font-weight: bold;
}
footer {
padding: 15px;
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
gap: 10px;
}
.input-row { display: flex; gap: 10px; align-items: center; }
input {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 12px;
border-radius: 12px;
color: white;
outline: none;
font-size: 15px;
transition: all 0.3s;
}
input:focus {
border-color: #6366f1;
background: rgba(255, 255, 255, 0.08);
box-shadow: 0 0 10px rgba(99, 102, 241, 0.2);
}
#name-input { width: 100px; }
#msg-input { flex: 1; }
button {
background: #6366f1;
color: white;
border: none;
padding: 12px 18px;
border-radius: 12px;
cursor: pointer;
font-weight: bold;
font-size: 15px;
transition: all 0.2s;
}
button:hover { background: #4f46e5; transform: scale(1.02); }
button:active { transform: scale(0.98); }
.btn-img { background: #e11d48; }
.btn-img:hover { background: #be123c; }
.chat-image { max-width: 100%; max-height: 150px; border-radius: 8px; margin-top: 5px; display: block; }
</style>
</head>
<body>
<header>
<div class="pulse"></div>
<h2>FREE CHAT HUB</h2>
</header>
<div id="chat-container"></div>
<footer>
<div class="input-row">
<input type="text" id="name-input" placeholder="Name..." maxlength="10">
<input type="text" id="msg-input" placeholder="Type a message..." maxlength="100">
<!-- Hidden File Input for Image -->
<input type="file" id="file-input" accept="image/*" style="display:none" onchange="compressAndSendImage(event)">
<button class="btn-img" onclick="document.getElementById('file-input').click()">📷</button>
<button onclick="sendMessage()">Send</button>
</div>
</footer>
<script>
if(localStorage.getItem('chat_user')) {
document.getElementById('name-input').value = localStorage.getItem('chat_user');
} else {
document.getElementById('name-input').value = "User_" + Math.floor(Math.random() * 900 + 100);
}
let lastMessageCount = 0;
// Messages load karne ka function
async function loadMessages() {
try {
let res = await fetch('/getMessages');
let data = await res.json();
let container = document.getElementById('chat-container');
let currentName = document.getElementById('name-input').value.trim();
if (data.length !== lastMessageCount) {
container.innerHTML = '';
data.forEach(msg => {
let isSelf = msg.sender === currentName;
let div = document.createElement('div');
div.className = `msg ${isSelf ? 'self' : 'other'}`;
// Check karein ki message normal text hai ya image data URL
let isImg = msg.text.startsWith('data:image/');
let msgContent = isImg ? `<img src="${msg.text}" class="chat-image" />` : escapeHTML(msg.text);
div.innerHTML = `<div class="meta">${escapeHTML(msg.sender)}</div><div>${msgContent}</div>`;
container.appendChild(div);
});
container.scrollTop = container.scrollHeight;
lastMessageCount = data.length;
}
} catch (e) { console.log("Connection lost"); }
}
// Text Message send karne ka function (POST Request)
async function sendMessage() {
let nameField = document.getElementById('name-input');
let msgField = document.getElementById('msg-input');
let name = nameField.value.trim();
let text = msgField.value.trim();
if(!name || !text) return;
localStorage.setItem('chat_user', name);
msgField.value = '';
let bodyData = new URLSearchParams();
bodyData.append('sender', name);
bodyData.append('text', text);
await fetch('/sendMessage', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: bodyData
});
loadMessages();
}
// Image compress karne aur use send karne ka main function
function compressAndSendImage(event) {
let file = event.target.files[0];
if (!file) return;
let name = document.getElementById('name-input').value.trim();
if (!name) { alert("Pehle apna Name likhein!"); return; }
localStorage.setItem('chat_user', name);
let reader = new FileReader();
reader.onload = function(e) {
let img = new Image();
img.onload = async function() {
let canvas = document.createElement('canvas');
let max_size = 150; // Image ki maximum width ya height 150px hogi
let width = img.width;
let height = img.height;
if (width > height) {
if (width > max_size) { height *= max_size / width; width = max_size; }
} else {
if (height > max_size) { width *= max_size / height; height = max_size; }
}
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Image ko low quality (0.15) JPEG me convert karein (Super Low Size)
let base64Img = canvas.toDataURL('image/jpeg', 0.15);
// ESP32 ko POST request se bhejhein
let bodyData = new URLSearchParams();
bodyData.append('sender', name);
bodyData.append('text', base64Img);
await fetch('/sendMessage', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: bodyData
});
// Input clear karein taaki same image dobara select ho sake
document.getElementById('file-input').value = '';
loadMessages();
}
img.src = e.target.result;
}
reader.readAsDataURL(file);
}
document.getElementById('msg-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
function escapeHTML(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
}
setInterval(loadMessages, 1000);
loadMessages();
</script>
</body>
</html>
)=====";
// Captive Portal Handling
void handleNotFound() {
String host = server.hostHeader();
if (host != "192.168.4.1" && host != "localhost") {
server.sendHeader("Location", "http://192.168.4.1/", true);
server.send(302, "text/plain", "");
} else {
server.send(200, "text/html", HTML_INDEX);
}
}
void setup() {
Serial.begin(115200);
// 1. AP Mode Setup
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
WiFi.softAP(ssid);
Serial.println("Wi-Fi 'FREE CHAT' Started!");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
// 2. DNS Server Setup
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(DNS_PORT, "*", apIP);
// 3. Web Server Routes
server.on("/", HTTP_GET, []() {
server.send(200, "text/html", HTML_INDEX);
});
// API: Fetch chat log
server.on("/getMessages", HTTP_GET, []() {
String json = "[";
for (size_t i = 0; i < chatHistory.size(); i++) {
json += "{\"sender\":\"" + escapeJSON(chatHistory[i].sender) + "\",\"text\":\"" + escapeJSON(chatHistory[i].text) + "\"}";
if (i < chatHistory.size() - 1) json += ",";
}
json += "]";
server.send(200, "application/json", json);
});
// API: Receive new message/image via POST
server.on("/sendMessage", HTTP_POST, []() {
if (server.hasArg("sender") && server.hasArg("text")) {
Message msg;
msg.sender = server.arg("sender");
msg.text = server.arg("text");
if (chatHistory.size() >= MAX_MESSAGES) {
chatHistory.erase(chatHistory.begin()); // Save RAM
}
chatHistory.push_back(msg);
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Bad Request");
}
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("Web Server Started!");
}
void loop() {
dnsServer.processNextRequest();
server.handleClient();
delay(2);
}
Comments
Post a Comment